/***************************************************************************
 *   This is a part of the NextCAD Web APP.                                *
 *   Copyright (C) AMCAX, All rights reserved.                             *
 ***************************************************************************/

import {
  BaseCommand,
  IAssemblyConstraint,
  pickedFaceData,
  pickedLineData,
  pickedVertexData,
} from '@amcax/base';
import { PickInputRJSFFormat } from '@app-cad/common/components/command-form/constants';
import { PickableCommandForm } from '@app-cad/common/components/command-form/pickable';
import { PRODUCT_EVENT } from '@app-cad/common/Constants';
import { useCommand } from '@app-cad/common/hooks/useCommand';
import { useTools } from '@app-cad/common/hooks/useTools';
import { DocumentContext } from '@app-cad/common/providers/documentProvider';
import {
  arePointsCollinear,
  arePointsCoplanar,
  arePointsOnCircle,
} from '@app-cad/common/utils';
import { MessageTip } from '@app-cad/common/utils/MessageTip';
import { getIdOfPickedObj } from '@app-cad/common/utils/render';
import {
  get1stLevelPartFromId,
  getAllConstraints,
  getParamsId,
} from '@app-cad/environments/product/utils';
import { ctx2, userEvents } from '@app-cad/module';
import { wsApi } from '@app-cad/server';
import { FeaturedCmdDialogParams } from '@app-cad/types';
import { RJSFSchema, UiSchema } from '@rjsf/utils';
import validator from '@rjsf/validator-ajv8';
import { useAsyncEffect, useMemoizedFn } from 'ahooks';
import { Button, Space } from 'antd';
import i18n from 'i18next';
import { difference } from 'lodash';
import { useContext, useEffect, useMemo, useState } from 'react';
import { AssemblyPreview } from '../../types/preview-types';
import { useAssemblyPreview } from '../common/AssemblyPreviewAnimator';
import {
  allConstraintList,
  constraintList,
  constraintMap,
  surfaceList,
  SurfaceType,
  TypesToShowVecSense,
} from './constant';
import './index.scss';
import { ConstraintType } from './type';

type pickedAllData = (pickedLineData & pickedFaceData) & pickedVertexData;

type FormData = {
  selectData1: pickedAllData;
  selectData2: pickedAllData;
  type: number;
  val?: number;
  vecSense?: 0 | 1 | 2; // 3 - `kReference` is not supported in core yet
};

interface CheckAndFinishOptions {
  preview?: boolean;
  continueAfterApply?: boolean; // 应用后继续，清空表单内容重置为初始状态
}

function ConstrainDialog({ title, onClose, props }: FeaturedCmdDialogParams) {
  const { docTree } = useContext(DocumentContext);
  const allConstraints = getAllConstraints(docTree);

  const checkDuplication = (item: string, target: string, type: number) => {
    if (!allConstraints) {
      return true;
    }

    return !allConstraints?.some((i) => {
      const obj = i?.originalObj as IAssemblyConstraint;
      return obj?.item === item && obj?.target === target && obj?.type === type;
    });
  };

  const areObjectsFromSamePart = () =>
    get1stLevelPartFromId(getIdOfPickedObj(formData.selectData1)) ===
    get1stLevelPartFromId(getIdOfPickedObj(formData.selectData2));

  const checkAndFinish = async (options: CheckAndFinishOptions = {}) => {
    const { preview = false, continueAfterApply = false } = options;

    if (areObjectsFromSamePart()) {
      return undefined;
    }

    let item = getIdOfPickedObj(formData.selectData1);
    let target = getIdOfPickedObj(formData.selectData2);
    const type = formData.type;
    const val = formData.val || 0;

    // vecSense default to 2 as parallel without direction specified
    const vecSense = formData.vecSense ?? 2;

    if (!item || !target || type < 0) {
      return undefined;
    }

    if (
      (!checkDuplication(item, target, type) ||
        !checkDuplication(target, item, type)) &&
      !props
    ) {
      MessageTip.error('创建失败，已有相同约束');
      return undefined;
    }
    item = getParamsId(item, formData.selectData1);
    target = getParamsId(target, formData.selectData2);
    const params = {
      item,
      target,
      type,
      val,
      vecSense,
    };

    if (!hasVecSense) {
      delete params.vecSense;
    }

    let executionResult: [AssemblyPreview, AssemblyPreview];
    if (props) {
      // edit constraint
      executionResult = await ctx2.commandCenters.assemblyCommands
        .editConstraint(
          {
            id: (props as any as IAssemblyConstraint).id,
            val,
            vecSense,
          },
          preview,
        )
        .catch((err) => {
          MessageTip.error(err.message);
        });
    } else {
      executionResult = await ctx2.commandCenters.assemblyCommands
        .addConstraint(params, preview)
        .catch((err) => {
          MessageTip.error(err.message);
        });
    }

    if (preview) {
      return executionResult;
    } else {
      userEvents.emit(PRODUCT_EVENT.UPDATE_CONSTRAINTS);
      if (continueAfterApply) {
        setFormData(initData);

        (
          document.querySelector(
            '.cmd-product-form-constraint-data-1 .pick-input_base-select',
          ) as HTMLDivElement
        )?.click(); // focus on the first input after applying
      } else {
        onClose();
      }
    }

    return undefined;
  };

  const { initEditDependency } = useCommand('cmd-constrain', {
    onClose,
    finishDraw: () => checkAndFinish(),
  });
  const initData: FormData = {
    selectData1: null,
    selectData2: null,
    type: -1,
    vecSense: 2,
  };

  const [formData, setFormData] = useState<FormData>(initData);
  const [hasVecSense, setHasVecSense] = useState(false);

  const { pickerAssembly } = useTools();

  useAsyncEffect(async () => {
    if (!props) {
      return;
    }

    const {
      item,
      target,
      val = 0,
      vecSense = 2,
      type = 0,
    } = props as any as IAssemblyConstraint;

    await initEditDependency([item, target]);
    const data = {
      selectData1: null,
      selectData2: null,
      type,
      val,
      vecSense,
    } as FormData;

    data.selectData1 = pickerAssembly.generatePickObjByPickID(item) as any;
    data.selectData2 = pickerAssembly.generatePickObjByPickID(target) as any;

    setFormData(data);
  }, []);

  const fetchPreview = useMemoizedFn(() => checkAndFinish({ preview: true }));

  useAssemblyPreview(fetchPreview, formData);

  const schema: RJSFSchema = useMemo(() => {
    const angleProperties =
      formData.type === ConstraintType.Angle
        ? { val: { type: 'number', title: '角度', default: 0 } }
        : {};

    const distanceProperties =
      formData.type === ConstraintType.Distance
        ? { val: { type: 'number', title: '距离', default: 0 } }
        : {};

    const tangentProperties =
      formData.type === ConstraintType.Tangent
        ? {
            val: {
              type: 'number',
              title: '相切方式',
              default: 1,
              enum: [-1, 1],
              enumNames: ['内切', '外切'],
            },
          }
        : {};

    const vecSenseProperties = hasVecSense
      ? {
          vecSense: {
            type: 'number',
            title: '朝向控制',
            default: 2,
            enum: [0, 1, 2],
            enumNames: ['同向', '反向', '默认'],
          } as any,
        }
      : {};

    return {
      type: 'object',
      properties: {
        selectData1: {
          type: 'string',
          title: '几何图形1:',
          format: PickInputRJSFFormat.PickALL,
        },
        selectData2: {
          type: 'string',
          title: '几何图形2:',
          format: PickInputRJSFFormat.PickALL,
        },
        type: {
          type: 'number',
          // title: '类型',
          oneOf: [
            ...constraintMap.map((i: any) => {
              return {
                title: (
                  <div className='constraintIcon-parent'>
                    {i.icon}
                    <span>{i.name}</span>
                  </div>
                ) as any as string,
                const: i.const,
              };
            }),
          ],
        },
        ...angleProperties,
        ...distanceProperties,
        ...tangentProperties,
        ...vecSenseProperties,
      },
    } as RJSFSchema;
  }, [formData.type, hasVecSense]);
  const [disableList, setDisabledList] = useState(allConstraintList);

  const getType = async (obj) => {
    if (obj?.edgeId) {
      const { edgeData = [] } = obj;

      if (arePointsCoplanar(edgeData)) {
        if (arePointsCollinear(edgeData)) {
          return 'Line';
        }

        if (arePointsOnCircle(edgeData)) {
          if (edgeData[0].equals(edgeData.at(-1))) {
            return 'Circle';
          }

          return 'ArcOfCircle';
        }

        return 'Planar Curve';
      }

      return 'Spatial Curve';
    } else if (obj?.meshId) {
      // 球体需要加上:f0
      const meshId =
        obj?.objInfo?.type === 'Part::Sphere' ? obj.meshId + ':f0' : obj.meshId;
      const res = await wsApi.getSurfaceType(meshId);
      return { surfaceType: res?.SurfaceType, radius: res?.radius };
    } else if (obj?.vertexId) {
      return 'vertex';
    }
    return null;
  };

  const updateFormSchemaBySelectedObjects = async () => {
    let selectData1Type = (await getType(formData.selectData1)) as any;
    let selectData2Type = (await getType(formData.selectData2)) as any;
    let select1Radius = 0;
    let select2Radius = 0;
    // 球,圆柱和圆面有半径信息
    if (selectData1Type?.surfaceType) {
      select1Radius = selectData1Type?.radius;
      selectData1Type = SurfaceType[selectData1Type?.surfaceType];
    }
    if (selectData2Type?.surfaceType) {
      select2Radius = selectData2Type?.radius;
      selectData2Type = SurfaceType[selectData2Type?.surfaceType];
    }
    let enabledList = constraintList.find(
      (i) =>
        (i.format1 === selectData1Type && i.format2 === selectData2Type) ||
        (i.format2 === selectData1Type && i.format1 === selectData2Type),
    )?.supportedConstraints;

    let disabledList = difference(allConstraintList, enabledList);

    //当选择圆弧与球面创建相切时，若圆弧半径≤球半径，允许创建相切;若圆弧半径大于球半径，则无法创建相切
    if (selectData1Type === 'ArcOfCircle' && selectData2Type === 'Sphere') {
      select1Radius = formData.selectData1?.objInfo?.params?.radius;
      if (select1Radius > select2Radius) {
        disabledList = [...disabledList, ConstraintType.Tangent];
      }
    }

    if (selectData1Type === 'Sphere' && selectData2Type === 'ArcOfCircle') {
      select2Radius = formData.selectData1?.objInfo?.params?.radius;
      if (select1Radius < select2Radius) {
        disabledList = [...disabledList, ConstraintType.Tangent];
      }
    }

    // 曲面包含多种面
    if (surfaceList.includes(selectData1Type)) {
      enabledList = constraintList.find(
        (i) =>
          (i.format1 === 'Surface' && i.format2 === selectData2Type) ||
          (i.format2 === 'Surface' && i.format1 === selectData2Type),
      )?.supportedConstraints;
      disabledList = difference(disabledList, enabledList);
    }

    if (surfaceList.includes(selectData2Type)) {
      enabledList = constraintList.find(
        (i) =>
          (i.format2 === 'Surface' && i.format1 === selectData1Type) ||
          (i.format1 === 'Surface' && i.format2 === selectData1Type),
      )?.supportedConstraints;
      disabledList = difference(disabledList, enabledList);
    }

    setDisabledList(disabledList);

    setHasVecSense(
      TypesToShowVecSense.find(
        (item) =>
          item.includes(selectData1Type) && item.includes(selectData2Type),
      ) !== undefined,
    );
  };

  useEffect(() => {
    if (!formData.selectData1 || !formData.selectData2) {
      setDisabledList(allConstraintList);
      setHasVecSense(false);
      return;
    }

    if (areObjectsFromSamePart()) {
      MessageTip.error('不允许选择同一零件');
      setDisabledList(allConstraintList);
      setHasVecSense(false);
      return;
    }
    // 编辑功能不支持 修改配合方式 及 判断是否需要朝向控制
    if (!props) {
      updateFormSchemaBySelectedObjects();
    }
  }, [formData.selectData1, formData.selectData2]);

  const uiSchema: UiSchema = {
    type: {
      'ui:widget': 'radio',
      'ui:classNames': 'cmd-product-form-constraint-type',
      'ui:enumDisabled': disableList,
      'ui:label': '',
    },
    selectData1: {
      'ui:placeholder': '请输入点/线/面',
      'ui:classNames': 'cmd-product-form-constraint-data-1',
      'ui:autofocus': true,
      'ui:disabled': !!props,
      'ui:options': {
        allowInfinitePlane: true,
      },
    },
    selectData2: {
      'ui:placeholder': '请输入点/线/面',
      'ui:disabled': !!props,
      'ui:options': {
        allowInfinitePlane: true,
      },
    },
    val: {
      'ui:widget':
        formData.type === ConstraintType.Tangent ? 'radio' : undefined,
    },
  };

  const isFormDataValid = useMemo(() => {
    const { selectData1, selectData2, type } = formData;
    return selectData1 != null && selectData2 != null && type != -1;
  }, [formData]);

  return (
    <PickableCommandForm
      title={title}
      schema={schema}
      onClose={onClose}
      formData={formData}
      extraUiSchema={uiSchema}
      validator={validator}
      onChange={setFormData}
    >
      <div className='flex justify-end pb-2 pr-2'>
        <Space>
          <Button onClick={onClose}>取消</Button>
          <Button
            type='primary'
            disabled={!isFormDataValid}
            onClick={() => checkAndFinish()}
          >
            确认
          </Button>
          {!props && ( // 编辑状态不支持应用并继续
            <Button
              type='primary'
              disabled={!isFormDataValid}
              onClick={() => checkAndFinish({ continueAfterApply: true })}
            >
              应用并继续
            </Button>
          )}
        </Space>
      </div>
    </PickableCommandForm>
  );
}

export const cmdConstrain: BaseCommand = {
  id: 'CmdConstrain',
  label: 'command.constrain',
  icon: 'constraints/assembly-constraint',
  create: (label: string, onClose: () => void, props) => (
    <ConstrainDialog
      title={i18n.t(label)}
      onClose={onClose}
      props={props}
      placement='topLeft'
    />
  ),
};
